package design.donkey.auto.ddl;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import design.donkey.auto.ddl.analysis.AnnotationAnalysis;
import design.donkey.auto.ddl.dialect.Dialect;
import design.donkey.auto.ddl.exception.AutoDdlException;
import design.donkey.auto.ddl.model.TableMetaModel;
import design.donkey.framework.common.util.CommonUtils;
import lombok.extern.slf4j.Slf4j;

/**
 * AutoDdl
 *
 * @author zhangjiakung
 * @since 0.0.1
 */
@Slf4j
public class AutoDdl {

    /**
     * 自动 DDL 类型
     */
    private final AutoDdlType autoDdlType;

    /**
     * 默认使用 dialect
     */
    private final Dialect dialect;

    /**
     * 解析器
     */
    private final AnnotationAnalysis analysis;

    /**
     * 是否格式化
     */
    private final boolean format;

    /**
     * 是否输出
     */
    private final boolean out;

    /**
     * 实例化
     *
     * @param dialect 默认的方言
     * @param analysis 解析器
     * @param format 是否格式化
     * @param out 是否输出
     */
    public AutoDdl(Dialect dialect, AnnotationAnalysis analysis, boolean format, boolean out) {
        this(AutoDdlType.NONE, dialect, analysis, format, out);
    }

    /**
     * 实例化
     *
     * @param autoDdlType autoDdlType
     * @param dialect 默认的方言
     * @param analysis 解析器
     * @param format 是否格式化
     * @param out 是否输出
     */
    public AutoDdl(AutoDdlType autoDdlType, Dialect dialect, AnnotationAnalysis analysis, boolean format, boolean out) {
        this.autoDdlType = autoDdlType;
        this.dialect = dialect;
        this.analysis = analysis;
        this.format = format;
        this.out = out;
    }

    /**
     * 对比数据库 与 domain 的变化；并执行维护语句
     *
     * @param dataSource 数据源用于获取 domain 在数据库中的表格信息及 执行 DDL
     * @param domainArray 目标 Schema SQL 脚本
     */
    public void execute(DataSource dataSource, Class<?>... domainArray) {
        execute(autoDdlType, dataSource, domainArray);
    }

    /**
     * 对比数据库 与 domain 的变化；并执行维护语句
     *
     * @param autoDdlTypeForTable 表格指定 的DDL 类型
     * @param dataSource 数据源用于获取 domain 在数据库中的表格信息及 执行 DDL
     * @param domainArray 目标 Schema SQL 脚本
     */
    public void execute(AutoDdlType autoDdlTypeForTable, DataSource dataSource, Class<?>... domainArray) {
        // 检查参数判断是否需要执行
        if (autoDdlTypeForTable == null || AutoDdlType.NONE.equals(autoDdlTypeForTable)) {
            return;
        }
        if (CommonUtils.isEmpty(domainArray)) {
            return;
        }

        // 解析ddl
        Map<Class<?>, Map<String, List<String>>> domainTableSchemaHub = new LinkedHashMap<>();
        for (Class<?> domain : domainArray) {
            try (Connection connection = dataSource.getConnection()) {
                Map<Class<?>, Map<String, List<String>>> maintenance =
                    maintenance(autoDdlTypeForTable, connection, domain);
                domainTableSchemaHub.putAll(maintenance);
            } catch (SQLException e) {
                throw new AutoDdlException(e);
            }
        }
        // 执行 ddl
        if (CommonUtils.isEmpty(domainTableSchemaHub)) {
            log.info("自动 DDL 类型为 {} 时，暂无需要执行的 DDL", autoDdlType);
            return;
        }
        try (Connection connection = dataSource.getConnection()) {
            for (Map.Entry<Class<?>, Map<String, List<String>>> domainTableSchemaEntry : domainTableSchemaHub
                .entrySet()) {
                // 当前 domain
                Class<?> domain = domainTableSchemaEntry.getKey();
                Map<String, List<String>> tableSchemaHub = domainTableSchemaEntry.getValue();
                for (Map.Entry<String, List<String>> tableSchemaEntry : tableSchemaHub.entrySet()) {
                    // domain 对应的表格信息
                    String table = tableSchemaEntry.getKey();
                    List<String> schemaDdlList = tableSchemaEntry.getValue();
                    executeDdl(autoDdlTypeForTable, connection, domain, table, schemaDdlList);
                }
            }
        } catch (SQLException e) {
            throw new AutoDdlException(e);
        }

    }

    /**
     * 对比数据库 与 domain 的变化
     *
     * @param autoDdlTypeForTable domain 的 DDL 类型；如果为空采用全局 DDl Type
     * @param con 数据库连接，用于获取数据中 domain 对应的表格信息
     * @param domain 目标 Schema SQL 脚本
     * @return 需要执行的 SQL 脚本列表
     */
    protected Map<Class<?>, Map<String, List<String>>> maintenance(AutoDdlType autoDdlTypeForTable, Connection con,
        Class<?> domain) {
        TableMetaModel tableMetaModel = analysis.analysis(domain);
        if (tableMetaModel == null) {
            return Collections.emptyMap();
        }
        try (Connection connection = con) {
            List<String> targetSchemaList = dialect.create(tableMetaModel, format);
            String sourceSchemaList = dialect.getCreateDdlByConnection(connection, tableMetaModel);
            Map<String, List<String>> tableSchemaDdlHub =
                maintenance(autoDdlTypeForTable, sourceSchemaList, String.join(";", targetSchemaList));
            // 根据 domain 分类
            Map<Class<?>, Map<String, List<String>>> domainTableSchemaDdlHub = new LinkedHashMap<>();
            domainTableSchemaDdlHub.put(domain, tableSchemaDdlHub);
            return domainTableSchemaDdlHub;
        } catch (SQLException e) {
            throw new AutoDdlException(e);
        }
    }

    /**
     * 对比两个 SQL schemas 脚本生成从 sourceSchemaSql 变化到 targetSchemaSql 需要执行 SchemaSQL 脚本。
     *
     * @param sourceSchemaSql 源 Schema SQL 脚本
     * @param targetSchemaSql 目标 Schema SQL 脚本
     * @return 需要执行的 SQL 脚本列表
     */
    public Map<String, List<String>> maintenance(String sourceSchemaSql, String targetSchemaSql) {
        return maintenance(autoDdlType, sourceSchemaSql, targetSchemaSql);
    }

    /**
     * 对比两个 SQL schemas 脚本生成从 sourceSchemaSql 变化到 targetSchemaSql 需要执行 SchemaSQL 脚本。
     *
     * @param autoDdlTypeForTable 表格的 DDL 类型；如果为空采用全局 DDl Type
     * @param sourceSchemaSql 源 Schema SQL 脚本
     * @param targetSchemaSql 目标 Schema SQL 脚本
     * @return 需要执行的 SQL 脚本列表
     */
    public Map<String, List<String>> maintenance(AutoDdlType autoDdlTypeForTable, String sourceSchemaSql,
        String targetSchemaSql) {
        AutoDdlType autoDdlTypeParams = autoDdlTypeForTable != null ? autoDdlTypeForTable : autoDdlType;
        return dialect.maintenance(sourceSchemaSql, targetSchemaSql, autoDdlTypeParams.getDdlOperations());
    }

    /**
     * execute schema ddl
     *
     * @param connection connection
     * @param domain 对应的 domain
     * @param table table
     * @param schemaDdlList schemaDdlList
     */
    protected void executeDdl(AutoDdlType autoDdlTypeForTable, Connection connection, Class<?> domain, String table,
        List<String> schemaDdlList) {
        if (AutoDdlType.VALIDATE.equals(autoDdlTypeForTable)) {
            log.warn("数据库 {} 与 Domain {} 不一致；需要执行的 DDL 为 {}", table, domain, String.join(";", schemaDdlList));
        } else {
            for (String schemaDdl : schemaDdlList) {
                try (Statement statement = connection.createStatement()) {
                    statement.execute(schemaDdl);
                    if (out) {
                        log.info("数据库 {} 与 Domain {} 执行的 DDL 成功 {}", table, domain, schemaDdl);
                    }
                } catch (SQLException e) {
                    log.error("数据库 {} 与 Domain {} 执行的 DDL 失败 {}", table, domain, schemaDdl, e);
                    throw new AutoDdlException(schemaDdl + "失败", e);
                }
            }
        }

    }

}
